#include "map.h"
#include "configuration.h"
#include "mapchunk.h"
#include "configfile.h"
#include "defines.h"

#include "controls/ftmessagebox.h"

#include "workers/workerprecachemap.h"
#include "workers/workerclearcache.h"

#include "ll-utm/LatLong-UTMconversion.h"

#define FLOAT_OFFSET 1000000.0

QStringList Map::used_ids;

Map::Map(const QString& file) :
    QRectF()
{
    initialize();
    QFileInfo qfi(file);
    if ( qfi.exists() )
    {
        if ( setMapId( qfi.fileName() ) )
        {
            _title = map_id;
            WorkerPrecacheMap runner(file,map_id, qfi.fileName() );
            if ( runner.run( ) )
            {
                QString tmp_file;
                bool load_ok = false;
                validMap = true;
                image_size = runner.getSize( chunk_size );
                calculateStaticMeasures();

                // Old FreeTrack CAL file:
                tmp_file = qfi.absoluteFilePath() + ".cal";
                if  ( !(load_ok = loadFreeTrackCal(tmp_file, cal_points)) )
                {
                    tmp_file = qfi.absolutePath() + "/" + qfi.completeBaseName() + ".map";
                    if ( !(load_ok = loadOziMap(tmp_file, cal_points)) )
                    {
                        tmp_file = qfi.absolutePath() + "/" + qfi.completeBaseName() + ".imp";
                        load_ok = loadCompeImp(tmp_file, cal_points);
                    }
                }
                if ( load_ok )
                    calibrateMap();
            }
        }
    }
}

Map::Map(QTextStream& stream) :
    QRectF()
{
    int version;

    initialize();
    QByteArray tmp, tmp_id;

    // Read version
    stream >> version;

    // Read map_id
    stream >> tmp_id;
    tmp_id.chop(1);
    tmp_id = tmp_id.mid(1);

    // Read title
    stream >> tmp;
    tmp.chop(1);
    tmp = tmp.mid(1);
    _title = QByteArray::fromBase64(tmp );

    // Read enabled
    QString isEnab;
    stream >> isEnab;
    _mapEnabled = (isEnab == "enabled");

    // Read image size
    int w,h;
    stream >> w;
    stream >> h;
    image_size = QSize( w,h );

    // read chunk_size
    stream >> chunk_size;

    // Read n. of calibration points
    int nCalPoints;
    stream >> nCalPoints;

    while (nCalPoints-- > 0 )
    {
        map_cal_point pt;
        stream >> pt.lat;
        stream >> pt.lon;
        stream >> pt.x;
        stream >> pt.y;
        cal_points.append( pt );
    }

    calculateStaticMeasures();
    calibrateMap();

    // Now set map id, which shall be unique...
    validMap = setMapId( QByteArray::fromBase64(tmp_id ) );
}

Map::~Map()
{
    used_ids.removeOne( map_id );
}

QString Map::serializeMap()
{
    QString string;
    QTextStream stream( &string );

    // Read version
    stream << 1 << ' ';

    // Read map_id
    stream << '"' << map_id.toAscii().toBase64() << '"' << ' ';

    // Read title
    stream << '"' << _title.toAscii().toBase64() << '"' << ' ';

    // Read enabled
    stream << (_mapEnabled ? "enabled " : "disabled ");

    // Read image size
    stream << image_size.width() << ' ';
    stream << image_size.height() << ' ';

    // read chunk_size
    stream << chunk_size << ' ';

    // Read n. of calibration points
    stream << cal_points.count() << ' ';

    for ( int i = 0; i < cal_points.count(); i++)
    {
        map_cal_point pt = cal_points.at(i);
        stream << pt.lat << ' ';
        stream << pt.lon << ' ';
        stream << pt.x << ' ';
        stream << pt.y << ' ';
    }
    return string;
}

void Map::initialize()
{
    image_size = QSize();
    chunk_size = 0;
    validMap = false;
    calibrated = false;
    _mapEnabled = true;
    map_id = _title = "";
    cal_points.clear();
    max_zoom = 99999;
}

bool Map::setMapId(const QString & s)
{
    if ( (map_id == "") && !used_ids.contains( s ) )
    {
        map_id = s;
        used_ids.append( s );
        return true;
    }
    FTMessageBox::Error( QObject::tr("Unable to load map"),
                        QObject::tr("Map with same name already loaded!") );
    return false;
}

bool Map::isCalibrated() const
{
    return calibrated;
}

bool Map::isValidMap() const
{
    return validMap;
}

bool Map::isEnabledMap() const
{
    return _mapEnabled;
}

void Map::enableMap(bool b)
{
    _mapEnabled = b;
}

QString Map::title() const
{
    return _title;
}

void Map::setTitle(const QString & t)
{
    _title = t;
}

int Map::bestZoomFactor(int proposed_zoom ) const
{
    if ( proposed_zoom < 1 )
        return 1;
    else if ( proposed_zoom > max_zoom )
        return max_zoom;
    else
        // Ensure max_zoom_factor is 1,2,4,8,16,32,64,128 or 256 ONLY
        return ( proposed_zoom > 256 ) ? 256 :
               ( proposed_zoom > 128 ) ? 256 :
               ( proposed_zoom > 64 ) ? 128 :
               ( proposed_zoom > 32 ) ? 64 :
               ( proposed_zoom > 16 ) ? 32:
               ( proposed_zoom > 8 ) ? 16 :
               ( proposed_zoom > 4 ) ? 8 :
               ( proposed_zoom > 2 ) ? 4 : proposed_zoom;
}

void Map::paint(QPainter& painter,
                const QTransform& wgs84_to_screen,
                const QRectF& visible_rect,
                double density_lat,
                double density_lon) const
{
    // For speed, skip checking for map enabled/calibrated (already done in MapsManager::createBlock)

    // First of all, transform the wgs84 visible area into the maps equivalent pixel area;
    QRect pixel_rect = wgs84_to_pixel.mapRect( visible_rect ).toRect();

    // Now, in order to find out which chunks we need, we need to calculate the best "zoom" factor.
    // We can do this by using the "density". Also, to get the best fitting zoom, check if the rectangle
    // aspect ratio is vertical or horizontal...
    int zoom_factor;
    if ( pixel_rect.width() >= pixel_rect.height() ) // horizontal? Use width...
        zoom_factor = bestZoomFactor( pixel_rect.width() / (visible_rect.width() * density_lon) );
    else // Vertical... use height...
        zoom_factor = bestZoomFactor( pixel_rect.height() / (visible_rect.height() * density_lat) );

    // Handy quickie to know how big in map pixel shall be the chunk:
    int real_chunk_size = chunk_size * zoom_factor;

    // Now calculate from which to which chunks we shall draw
    int max_x = qMin( pixel_rect.right() / real_chunk_size, max_cols.value( zoom_factor, 1) );
    int max_y = qMin( pixel_rect.bottom() / real_chunk_size, max_rows.value( zoom_factor, 1) );

    painter.save();
    // We combine the transformations: from image pixels to wgs + wgs to screen
    // So we get an overall "image pixel to screen" transformation matrix...
    painter.setWorldTransform( pixel_to_wgs84 * wgs84_to_screen, true );
    // Then we also apply a "zoom" scaling to compensate for >1 zoom factors:
    painter.scale( zoom_factor, zoom_factor );
    for (int x = pixel_rect.left() / real_chunk_size; x <= max_x; x++ )
    {
        for (int y = pixel_rect.top() / real_chunk_size; y <= max_y; y++ )
        {
            // Get the chunk (cached or loaded)
            const MapChunk* chunk = MapChunk::getChunk(x,y,zoom_factor, map_id );
            if ( chunk != NULL )
                // Draw the chunk on the block, but make sure we draw it at the proper place...
                painter.drawImage(x * chunk_size,
                                  y * chunk_size, *chunk );
        }
    }
    painter.restore();
}

void Map::paint(QPainter& painter,
                const QRect& visible_rect,
                const QRect& output_rect) const
{
    // First of all, get the really visible area of the map:
//    QRect pixel_rect = visible_rect.intersected( QRect( QPoint(0,0), image_size) );

    // Now, calculate the required zoom to best fit the pixel_rect on the output_rect:
    int zoom_factor;
    if ( visible_rect.width() >= visible_rect.height() ) // horizontal? Use width...
        zoom_factor = bestZoomFactor( visible_rect.width() / output_rect.width() );
    else // Vertical... use height...
        zoom_factor = bestZoomFactor( visible_rect.height() / output_rect.height() );

    // Handy quickie to know how big in map pixel shall be the chunk:
    int real_chunk_size = chunk_size * zoom_factor;

    // Now calculate from which to which chunks we shall draw
    int min_x = qMax( visible_rect.left() / real_chunk_size, 0);
    int min_y = qMax( visible_rect.top() / real_chunk_size, 0);
    int max_x = qMin( visible_rect.right() / real_chunk_size, max_cols.value( zoom_factor, 1)-1 );
    int max_y = qMin( visible_rect.bottom() / real_chunk_size, max_rows.value( zoom_factor, 1)-1 );

    painter.save();

    painter.translate( - visible_rect.x()/zoom_factor + output_rect.x(),
                       - visible_rect.y()/zoom_factor + output_rect.y() );

    for (int x = min_x; x <= max_x; x++ )
    {
        for (int y = min_y; y <= max_y; y++ )
        {
            // Get the chunk (cached or loaded)
            const MapChunk* chunk = MapChunk::getChunk(x,y,zoom_factor, map_id );
            if ( chunk != NULL )
            {
                // Draw the chunk on the block, but make sure we draw it at the proper place...
                painter.drawImage(x * chunk_size,
                                  y * chunk_size, *chunk );
            }
        }
    }
    painter.restore();
}

int Map::maxZoom() const
{
    return max_zoom;
}

bool Map::loadCalibration(const QString& filename, QList<map_cal_point>& points)
{
    if  ( !loadFreeTrackCal(filename, points) )
        if ( !loadOziMap(filename, points) )
            return loadCompeImp(filename, points);
    return true;
}

bool Map::loadOziMap(const QString& filename, QList<map_cal_point>& points)
{
    bool ret = false;
    // Check if file exists
    if ( (filename != "") && QFile::exists( filename) )
    {
        QFile file(filename);
        // Open file and try to read it
        if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
        {


            // Read points:
// Point01,xy,  223,  510,in, deg,  45,    0744,N,   7,   61447,W, grid,   ,           ,           ,N
// Point01,xy, 3757, 2377,in, deg,    ,        ,N,    ,        ,W, grid, 33,     369962,    5151606,N

 QRegExp rx_pt(
         // PointAB %1
 "^\\s*point(\\d+)\\s*,"\
  "\\s*xy\\s*,"\
  // imageX %2
  "\\s*(\\d*)\\s*,"\
   // imageY %3
  "\\s*(\\d*)\\s*,"\
  "\\s*in\\s*,\\s*deg\\s*,"\
  // main LAT degrees %4
  "\\s*("REGEXP_NUMBER")\\s*,"\
  // min/sec LAT degrees %5
  "\\s*("REGEXP_NUMBER")\\s*,"\
   // north/south %6
  "\\s*([ns])\\s*,"\
  // main LON degrees %7
  "\\s*("REGEXP_NUMBER")\\s*,"\
  // min/sec LON degrees %8
  "\\s*("REGEXP_NUMBER")\\s*,"\
  // east/west %9
  "\\s*([we])\\s*,"\
  "\\s*grid\\s*,"\
  // UTM grid n. %10
  "\\s*(\\d*)\\s*,"\
  // UTM easting %11
  "\\s*(\\d*)\\s*,"\
  // UTM norting % 12
  "\\s*(\\d*)\\s*,"\
  // UTM zone letter &13
  "\\s*([a-z])"\
  ".*");


            QRegExp rx_mmpxy("^\\s*mmpxy\\s*,"\
                             "\\s*(\\d+)\\s*,"\
                             "\\s*(\\d+)\\s*,"\
                             "\\s*(\\d+)\\s*");

            QRegExp rx_mmpll("^\\s*mmpll\\s*,"\
                             "\\s*(\\d+)\\s*,"\
                             "\\s*("REGEXP_NUMBER")\\s*,"\
                             "\\s*("REGEXP_NUMBER")\\s*");

            QList<QPoint> xy_list;
            QList<QPointF> ll_list;

            QString row;

            // Locate the points (Point01 to Point30):
            bool found = false;
            while ( !found && !file.atEnd() )
            {
                row = file.readLine().toLower();
                found = row.left(5) == "point";
            }

            // If we found the point section, load them all!
            while ( found && !file.atEnd() )
            {
                if ( rx_pt.indexIn( row ) > -1 )
                {
                    QString pt_n = rx_pt.cap(1);
                    QString x_str = rx_pt.cap(2);
                    QString y_str = rx_pt.cap(3);
                    if ( (x_str.trimmed() != "") && (y_str.trimmed() != "") )
                    {
                        map_cal_point pt;
                        pt.x = x_str.toInt();
                        pt.y = y_str.toInt();
                        pt.lat = pt.lon = 180.0;
//                        QPoint pt( x_str.toInt(), y_str.toInt() );
//                        QPointF pt_ll( 180, 180 );

                        QString minutes_seconds = rx_pt.cap(5).trimmed();
                        // Do we have a decimal value or a sessagesimal value?
                        if ( minutes_seconds.count('.') == 1 )
                        {
                            double minutes = minutes_seconds.toDouble();
                            minutes_seconds =  QString("%1").arg( minutes/60.0 ).mid(2).trimmed();
                        }
                        QString lat = QString("%1.%2").arg( rx_pt.cap(4)).arg( minutes_seconds );

                        minutes_seconds = rx_pt.cap(8).trimmed();
                        // Do we have a decimal value or a sessagesimal value?
                        if ( minutes_seconds.count('.') == 1 )
                        {
                            double minutes = minutes_seconds.toDouble();
                            minutes_seconds =  QString("%1").arg( minutes/60.0 ).mid(2).trimmed();
                        }
                        QString lon = QString("%1.%2").arg( rx_pt.cap(7)).arg( minutes_seconds );

                        if ( (lat.trimmed() != ".") && (lon.trimmed() != ".") )
                        {
                            pt.lon = lon.toDouble();
                            pt.lat = lat.toDouble();
                            QString lat_direction = rx_pt.cap(6);
                            QString lon_direction = rx_pt.cap(9);
                            if ( lat_direction == "s" )
                                pt.lat *= -1.0;
                            if ( lon_direction == "w" )
                                pt.lon *= -1.0;
                        }
                        else // go with UTM (if any)
                        {
                            QString utm_zone = rx_pt.cap(10) + rx_pt.cap(13).toUpper();
                            QString utm_easting = rx_pt.cap(11);
                            QString utm_norting = rx_pt.cap(12);
                            if ( (utm_easting.trimmed() != "" ) && (utm_norting.trimmed() != "" ) )
                            {
                                qreal easting = utm_easting.toDouble();
                                qreal northing = utm_norting.toDouble();
                                UTMtoLL( 23, northing,
                                             easting,
                                             utm_zone.toAscii(),
                                             pt.lat,pt.lon);
                            }
                        }
                        if ( pt.lon < 180.0 )
                        {
                            points.append( pt );
                        }
                    }
                }
                row = file.readLine().toLower();
                found = row.left(5) == "point";
            }

            // Locate the moving maps scetion (if any) (MMPXY or MMPLL):
            found = false;
            while ( !found && !file.atEnd() )
            {
                row = file.readLine().toLower();
                found = (row.left(5) == "mmpxy") || (row.left(5) == "mmpll");
            }

            // If we found the moving map section, load it!
            QHash<QString, map_cal_point> mmpts;
            while ( !file.atEnd() )
            {
                if ( found )
                {
                    if ( rx_mmpxy.indexIn( row ) > -1 )
                    {
                        QString n = rx_mmpxy.cap(1);
                        QString x = rx_mmpxy.cap(2);
                        QString y = rx_mmpxy.cap(3);
                        map_cal_point pt;
                        if ( mmpts.contains( n ) )
                            pt = mmpts.value( n );
                        else
                            pt.lon = pt.lat = 180.0;
                        pt.x = x.toInt();
                        pt.y = y.toInt();
                        mmpts.insert( n, pt );
                    }
                    else if ( rx_mmpll.indexIn( row ) > -1 )
                    {
                        QString n = rx_mmpll.cap(1);
                        QString lon = rx_mmpll.cap(2);
                        QString lat = rx_mmpll.cap(3);
                        map_cal_point pt;
                        if ( mmpts.contains( n ) )
                            pt = mmpts.value( n );
                        else
                            pt.x = pt.y = -1;
                        pt.lon = lon.toDouble();
                        pt.lat = lat.toDouble();
                        mmpts.insert( n, pt );
                    }
                }
                row = file.readLine().toLower();
                found = (row.left(5) == "mmpxy") || (row.left(5) == "mmpll");
            }

            foreach ( map_cal_point pt, mmpts )
            {
                if ( (pt.x > -1) && (pt.lon < 180.0) )
                    points.append( pt );
            }
            // We want at least 3 points to proceed...
            if ( points.count() >= 3 )
            {
                ret = true;
            } // if al least 3 points
        } // if open ok
    } // if file exists
    return ret;
}

bool Map::loadFreeTrackCal(const QString&filename, QList<map_cal_point> & points)
{
    QFileInfo qfi( filename );
    // check extension...
    if ( ( qfi.suffix() != "cal" ) || !qfi.exists() )
        return false;

    // Open config file
    ConfigFile config( filename, QIODevice::ReadOnly );
    // Proceed only if calibration file exist
    if ( config.getStatus() )
    {

        config.readRow( "title" );
        config.readRow( "description" );

        QRegExp rx_point("\\s*(\\d+),(\\d+)\\s+("REGEXP_NUMBER"),("REGEXP_NUMBER")\\s*$");

        points.clear();
        for (int p = 0; p < 50; p++ )
        {
            QString row_name = QString("point%1").arg(p);
            if ( config.readRow( row_name, rx_point ) )
            {
                map_cal_point npt;
                npt.x = ((QString)rx_point.cap(1)).toInt();
                npt.y = ((QString)rx_point.cap(2)).toInt();
                npt.lon = ((QString)rx_point.cap(4)).toDouble();
                npt.lat = ((QString)rx_point.cap(3)).toDouble();
                points.append( npt );
            }
        }
        // cal file is considered valid ONLY if we got at least 3 points...
        return (points.size() >=3 );
    }
    return false;
}

bool Map::loadCompeImp(const QString&filename, QList<map_cal_point>& points)
{
    bool ret = false;

    // Check if file exists
    if ( (filename != "") && QFile::exists( filename) )
    {
        QFile file(filename);
        // Open file and try to read it
        if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
        {
            QString row;

            bool found = false;
            // Look for the <Calibration> section...
            while ( !file.atEnd() )
            {
                row = file.readLine();
                if ( row.contains("<Calibration>") )
                {
                    found = true;
                    break;
                }
            }
            if ( found )
            {
                // Now we can read all the points...
                // P0=2568.10000000,1300.20000000,A,7.39803333,45.11053333
                // P0=258.60000000,5737.20000000,32T,340000.00000000,4968000.00000000
                QRegExp rx_pt("^\\s*P\\d+\\s*=\\s*(\\d+\\.?\\d*)\\s*,\\s*(\\d+\\.?\\d*)\\s*,\\s*(\\w+)\\s*,\\s*("REGEXP_NUMBER")\\s*,\\s*("REGEXP_NUMBER")\\s*$");
                for (int pts = 0; pts < 30; pts++ )
                {
                    row = file.readLine();
                    rx_pt.indexIn( row );
                    QString x = rx_pt.cap(1);
                    QString y = rx_pt.cap(2);
                    QString utm_zone = rx_pt.cap(3);
                    QString lat = QString("%1").arg( rx_pt.cap(5));
                    QString lon = QString("%1").arg( rx_pt.cap(4));
                    if ( (x != "") && (y != "") &&
                         (lat != "") && (lon != "") )
                    {
                        map_cal_point pt;
                        pt.x = x.toDouble();
                        pt.y = y.toDouble();
                        if ( utm_zone == "A" )
                        {
                            pt.lon = lon.toDouble();
                            pt.lat = lat.toDouble();
                        }
                        else
                        {
                            UTMtoLL( 23, lat.toDouble(),
                                         lon.toDouble(),
                                         utm_zone.toAscii(),
                                         pt.lat,pt.lon);
                        }
                        points.append( pt );
                    }
                }
                int np = points.count();
                if ( np >= 3 )
                {
                    ret = true;
                } // if al least 3 points
            } // if Calibration section found
        } // if open ok
    } // if file exists
    return ret;
}

void Map::calibrateMap()
{
    calibrated = false;

    if ( cal_points.count() < 3 )
        return;

    qreal Xi1 = cal_points.at(0).x;
    qreal Yi1 = cal_points.at(0).y;
    qreal Xi2 = cal_points.at(1).x;
    qreal Yi2 = cal_points.at(1).y;
    qreal Xi3 = cal_points.at(2).x;
    qreal Yi3 = cal_points.at(2).y;

    // Due to limited Symbian float precision, we need to multiply this by the OFFSET
    qreal X1 = FLOAT_OFFSET*cal_points.at(0).lon;
    qreal Y1 = FLOAT_OFFSET*cal_points.at(0).lat;
    qreal X2 = FLOAT_OFFSET*cal_points.at(1).lon;
    qreal Y2 = FLOAT_OFFSET*cal_points.at(1).lat;
    qreal X3 = FLOAT_OFFSET*cal_points.at(2).lon;
    qreal Y3 = FLOAT_OFFSET*cal_points.at(2).lat;

    // Due to the conversion equations used, points 3 and 1 shall not be on the same X line.
    // So exchange them
    if ( (Y1 == Y3) || (Yi1 == Yi3) )
    {
        qSwap( X2, X3 );
        qSwap( Y2, Y3 );
        qSwap( Xi2, Xi3 );
        qSwap( Yi2, Yi3 );
    }

    // We must avoid points n.1 and 3 on the same line. If this happen, the transformation
    // matrix will be "broken" due to some internal Qt precision problems on Symbian!
    if ( ( ( fabs((X3-X1) / (Y3 - Y1 ) - (X2 -X1) / (Y2 - Y1)) < 0.01 ) ) ||
         ( ( fabs((Xi3-Xi1) / (Yi3 - Yi1 ) - (Xi2 -Xi1) / (Yi2 - Yi1)) < 0.01 ) ) )
    {
        X2 = X1;
        Y2 = Y3;
        Xi2 = Xi1;
        Yi2 = Yi3;
    }

    qreal dXi31 = Xi3 - Xi1;
    qreal dYi31 = Yi3 - Yi1;
    qreal dX31 = X3 - X1;
    qreal dY31 = Y3 - Y1;
    qreal dX21 = X2 - X1;
    qreal dY21 = Y2 - Y1;
    qreal dXi21 = Xi2 - Xi1;
    qreal dYi21 = Yi2 - Yi1;

    // Here we need to remove the offset...
    qreal m11, m22, m21, m12;

    m12 = ( dYi21 * dY31 - dY21 * dYi31 ) / ( dX21 * dY31 - dX31 * dY21 ) * FLOAT_OFFSET;
    m21 = ( dX31 * dXi21 - dX21 * dXi31 ) / ( dY21 * dX31 - dX21 * dY31 ) * FLOAT_OFFSET;

    // Here we need to remove the offset again...
    m11 = dXi31 * FLOAT_OFFSET / dX31 - m21 * dY31 / dX31 ;
    m22 = dYi31 * FLOAT_OFFSET / dY31 - m12 * dX31 / dY31;

    // Here we need to remove the offset again...
    qreal m31 = Xi1 - m11 * X1 / FLOAT_OFFSET - m21 * Y1 / FLOAT_OFFSET;
    qreal m32 = Yi1 - m22 * Y1 / FLOAT_OFFSET - m12 * X1 / FLOAT_OFFSET;

    qreal m13 = 0;
    qreal m23 = 0;
    qreal m33 = 1;

    wgs84_to_pixel  = QTransform( m11, m12, m13, m21, m22, m23, m31, m32, m33 );
    pixel_to_wgs84 = wgs84_to_pixel.inverted();

    QRectF r = pixel_to_wgs84.mapRect( QRectF( QPointF(0,0), image_size ));
    setTopLeft( r.topLeft() );
    setBottomRight( r.bottomRight() );
    calibrated = true;
}

void Map::calculateStaticMeasures()
{
    // NOTE: all these only requires image size and chunk size to be known.

    // We need to know which is the maximum zoom level
    if ( chunk_size == 0 )
        return;

    max_zoom = bestZoomFactor( image_size.width() / chunk_size );
    max_cols.clear();
    max_rows.clear();
    for (int z = 1; z <= max_zoom; z *= 2)
    {
        max_cols.insert( z, ceil( (double)image_size.width() / (double)(chunk_size * z) ) );
        max_rows.insert( z, ceil( (double)image_size.height() / (double)(chunk_size * z) ) );
    }
}

void Map::clearCache()
{
    WorkerClearCache runner(map_id, _title );
    runner.run();
}

void Map::calibrationPoints(QList<map_cal_point> & cp) const
{
    cp = cal_points;
}

void Map::setCalibrationPoints(const QList<map_cal_point> & cp)
{
    cal_points = cp;
    calibrateMap();
}

QSize Map::pixelSize() const
{
    return image_size;
}
